package com.cloud.api.domain.algorithm;

import cn.hutool.core.collection.CollUtil;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 杨斌
 * 2025-01-2025-01-21
 */

public class Aggregation {

    private double r = 500;



    /**
     * 缺点：各个点迭代顺序不同导致最终结果会有差异
     */
    public List<VehiclePoint> directDistance(List<VehiclePoint> points) {

        // 聚合簇结果集
        List<Cluster> clusters = new ArrayList<>();
        int count = 0;
        // 遍历所有的点
        for (VehiclePoint point : points) {
            // 判断是否有聚合簇可以聚合该点
            Cluster isCluster = null;
            for (Cluster cluster : clusters) {
                // 判断点是否在聚合簇的矩形范围内,如果在的话才进行下一步的距离判断
                // 优化点：不以圆来判断,就使用矩形进行范围判断
                if(cluster.getLatLngBounds().contains(point)) {
                    double distance = calculateLineDistance(point, cluster.getCenterPoint());
                    // 判断该点和聚合簇中心点之间距离是否小于指定半径(可能有多个聚合簇满足条件,选择相离最近的一个进行聚合)
                    // 优化点：就以第一个相邻的聚合簇进行聚合
                    if (distance < r && (isCluster == null || calculateLineDistance(point, cluster.getCenterPoint()) <
                            calculateLineDistance(point, isCluster.getCenterPoint()))) {
                        isCluster = cluster;
                    }
                }
                count++;
            }

            if (isCluster != null) {
                // 该点可以被聚合簇进行聚合
                isCluster.addCount();
                isCluster.getPoints().add(point);
                // 重置中心点和矩形经纬度范围(比较耗时)
                // 优化点：就以第一个点的坐标作为中心进行聚合
                isCluster.setCenterPoint(getCenterPoint(isCluster.getPoints()));
                isCluster.setLatLngBounds(LatLngBounds.builder()
                        // 东北角：纬度增加，经度增加。
                        //西南角：纬度减少，经度减少。
                        .northeast(VehiclePoint.builder().lat(point.getLat() + km2Degree(r / 1000))
                                .lng(point.getLng() + km2Degree(r / 1000)).build())
                        .southwest(VehiclePoint.builder().lat(point.getLat() - km2Degree(r / 1000))
                                .lng(point.getLng() - km2Degree(r / 1000)).build()).build());
            } else {
                // 该点不可以被聚合簇进行聚合,单独作为一个聚合簇添加到集合中
                clusters.add(Cluster.builder()
                        .centerPoint(point) // 设置聚合簇的中心点为当前点
                        .points(CollUtil.newLinkedList(point)) // 初始化包含当前点的点列表
                        .count(1) // 聚合簇中的车辆计数初始化为1
                        .latLngBounds(LatLngBounds.builder()
                                .northeast(VehiclePoint.builder() // 定义矩形的东北角
                                        .lat(point.getLat() + km2Degree(r / 1000)) // 东北角纬度 = 当前点纬度 + 度数变化量
                                        .lng(point.getLng() + km2Degree(r / 1000)) // 东北角经度 = 当前点经度 + 度数变化量
                                        .build())
                                .southwest(VehiclePoint.builder() // 定义矩形的西南角
                                        .lat(point.getLat() - km2Degree(r / 1000)) // 西南角纬度 = 当前点纬度 - 度数变化量
                                        .lng(point.getLng() - km2Degree(r / 1000)) // 西南角经度 = 当前点经度 - 度数变化量
                                        .build())
                                .build())
                        .build());
            }
        }

        // 是否出现半径500米内集中停放超过60天且SOC大于80%，车辆总数大于50辆的情况
        // 收集所有符合聚集条件的车辆
        List<VehiclePoint> aggregatedPoints = new ArrayList<>();
        for (Cluster cluster : clusters) {
            if(cluster.getPoints().size() > 50){
                List<VehiclePoint> clusterPoints = cluster.getPoints();
                aggregatedPoints.addAll(clusterPoints);
            }
        }
        return aggregatedPoints;
    }

    /**
     * 计算两点的距离
     */
    private double calculateLineDistance(VehiclePoint source, VehiclePoint target) {
        double sourceLongitude = source.getLng();
        double sourceLatitude = source.getLat();
        double targetLongitude = target.getLng();
        double targetLatitude = target.getLat();
        sourceLatitude = sourceLatitude * Math.PI / 180.0;
        targetLatitude = targetLatitude * Math.PI / 180.0;
        //地球半径
        double r = 6378137;
        double a = sourceLatitude - targetLatitude;
        double b = (sourceLongitude - targetLongitude) * Math.PI / 180.0;
        double sa2 = Math.sin(a / 2.0);
        double sb2 = Math.sin(b / 2.0);
        return 2 * r * Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(sourceLatitude) * Math.cos(targetLatitude) * sb2 * sb2));
    }

    /**
     * 取多个经纬度的中心点，计算所有点的平均位置
     */
    public VehiclePoint getCenterPoint(List<VehiclePoint> pointList) {

        int total = pointList.size();
        double X = 0, Y = 0, Z = 0;

        for (VehiclePoint point : pointList) {
            double lat, lng, x, y, z;
            lat = point.getLat() * Math.PI / 180;
            lng = point.getLng() * Math.PI / 180;
            x = Math.cos(lat) * Math.cos(lng);
            y = Math.cos(lat) * Math.sin(lng);
            z = Math.sin(lat);
            X += x;
            Y += y;
            Z += z;
        }

        X = X / total;
        Y = Y / total;
        Z = Z / total;
        double Lon = Math.atan2(Y, X);
        double Hyp = Math.sqrt(X * X + Y * Y);
        double Lat = Math.atan2(Z, Hyp);
        double longitude = Lon * 180 / Math.PI;
        double latitude = Lat * 180 / Math.PI;
        return VehiclePoint.builder().lng(longitude).lat(latitude).build();
    }

    /**
     * 地球半径:km
     */
    public final double earthRadius = 6371.393;

    /**
     * 将公里数转换为度数
     */
    public Double km2Degree(Double l) {
        //公式:l(弧长)=degree（圆心角）× π（圆周率）× r（半径）/180
        //转换后的公式：degree（圆心角）=l(弧长) × 180/(π（圆周率）× r（半径）)
        return (180 / earthRadius / Math.PI) * l;
    }

    /**
     * 将度数转换为公里数
     */
    public Double degree2Km(Double degree) {
        //公式:l(弧长)=degree（圆心角）× π（圆周率）× r（半径）/180
        //将180放在前面，降低数值
        return earthRadius / 180 * Math.PI * degree;
    }

}
